import crypto from "crypto";

// nodejs8没有base64url这种encoding，此处为对应的polyfill
function toBase64Url(str: string) {
    return str.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
}

function fromBase64Url(base64url: string) {
    base64url = base64url.toString();
    var padding = 4 - (base64url.length % 4);
    if (padding !== 4) {
        for (var i = 0; i < padding; ++i) {
            base64url += "=";
        }
    }
    return base64url.replace(/-/g, "+").replace(/_/g, "/");
}

function atob(str: string) {
    return Buffer.from(str, "base64").toString("utf-8");
}

function btoa(str: string) {
    return Buffer.from(str, "utf-8").toString("base64");
}

function fromToken(base64url: string) {
    return JSON.parse(atob(fromBase64Url(base64url)));
}

function toToken(obj: { [k: string]: any }) {
    return toBase64Url(btoa(JSON.stringify(obj)));
}

function getSign(str: string, secret: string) {
    return toBase64Url(crypto.createHmac("sha256", secret).update(str).digest("base64"));
}

const jwt = {
    verify: function (token: string, secret: string) {
        if (typeof token !== "string") {
            throw new Error("Invalid token");
        }
        const tokenArr = token.split(".");
        if (tokenArr.length !== 3) {
            throw new Error("Invalid token");
        }
        const [headerBase64, payloadBase64, signatureBase64] = tokenArr;
        if (getSign(headerBase64 + "." + payloadBase64, secret) !== signatureBase64) {
            throw new Error("Invalid token");
        }
        const header = fromToken(headerBase64);
        if (header.alg !== "HS256" || header.typ !== "JWT") {
            throw new Error("Invalid token");
        }
        const payload = fromToken(payloadBase64);

        if (payload.exp * 1000 < Date.now()) {
            const err = new Error("Token expired");
            err.name = "TokenExpiredError";
            throw err;
        }
        return payload;
    },
    sign: function (data: { [k: string]: any }, secret: string, expiresIn: number) {
        if (!expiresIn) {
            throw new Error("expiresIn is required");
        }
        const header = {
            alg: "HS256",
            typ: "JWT",
        };
        // 要秒
        const now = parseInt(`${Date.now() / 1000}`);
        const payload = {
            ...data,
            iat: now,
            // 过期的也是秒
            exp: now + expiresIn,
        };
        const signStr = toToken(header) + "." + toToken(payload);
        const signature = getSign(signStr, secret);
        return signStr + "." + signature;
    },
};

export { jwt };
